/**
 * This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
 */
package com.softwarecraftsmen.collections.lists;

import com.softwarecraftsmen.collections.IndexExceedsHighestKnownIndexException;
import com.softwarecraftsmen.collections.IndexLessThanZeroException;
import com.softwarecraftsmen.collections.tuples.Pair;
import static com.softwarecraftsmen.functors.Boolean.False;
import static com.softwarecraftsmen.functors.Boolean.True;
import static com.softwarecraftsmen.functors.Boolean.fromLegacy;
import com.softwarecraftsmen.functors.Boolean;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;

public class LinkedList<V> implements MutableList<V>
{
	private int size;
	private Entry<V> first;
	private Entry<V> last;
	private static final int Zero = 0;

	public LinkedList()
	{
		this(Zero, null, null);
	}

	private LinkedList(final int size, final @Nullable Entry<V> first, final @Nullable Entry<V> last)
	{
		this.size = size;
		this.first = first;
		this.last = last;
	}

	@NotNull
	public com.softwarecraftsmen.functors.Boolean isEmpty()
	{
		return fromLegacy(size == Zero);
	}

	@NotNull
	public Boolean hasElements()
	{
		return fromLegacy(size != Zero);
	}

	@NotNull
	public V valueAt(final Integer key)
	{
		if (key < Zero)
		{
			throw new IndexLessThanZeroException(key);
		}
		if (key >= size)
		{
			throw new IndexExceedsHighestKnownIndexException(key);
		}
		Entry<V> current = findEntry(key);
		return current.data;
	}

	@NotNull
	public ArrayList<V> toLegacyArrayList()
	{
		return (ArrayList<V>) populate(new ArrayList<V>(size));
	}

	@NotNull
	public java.util.LinkedList<V> toLegacyLinkedList()
	{
		return (java.util.LinkedList<V>) populate(new java.util.LinkedList<V>());
	}

	@SuppressWarnings({"unchecked"})
	@NotNull
	public V[] toLegacyArray()
	{
		return (V[]) toLegacyArrayList().toArray();
	}

	private java.util.List<V> populate(final java.util.List<V> list)
	{
		Entry<V> entry = first;
		while(entry != null)
		{
			list.add(entry.data);
			entry = entry.next;
		}
		return list;
	}

	public int size()
	{
		return Zero;
	}

	@NotNull
	public Boolean contains(final @NotNull Integer key)
	{
		return fromLegacy(-1 < key).and(fromLegacy(key < size));
	}

	@NotNull
	public Boolean doesNotContain(final @NotNull Integer key)
	{
		return contains(key).not();
	}

	@NotNull
	public Boolean isMutable()
	{
		return True;
	}

	public void replaceIfPresent(final @NotNull Integer key, final @NotNull V value)
	{
		findEntry(key).data = value;
	}

	public void replaceIfPresent(final @NotNull Pair<Integer, V> entry)
	{
		replaceIfPresent(entry.a, entry.b);
	}

	public void add(final @NotNull V value)
	{
		if (size == 0)
		{
			first = new Entry<V>(value);
			last = first;
		}
		else
		{
			final Entry<V> entry = new Entry<V>(value);
			entry.previous = last;
			last.next = entry;
			last = entry;
		}
		size++;
	}

	@NotNull
	public List<V> defensiveCopy()
	{
		final LinkedList<V> list = new LinkedList<V>();
		Entry<V> entry = first;
		while (entry != null)
		{
			list.add(entry.data);
			entry = entry.next;
		}
		return list;
	}

	@NotNull
	public Boolean isOrdered()
	{
		return True;
	}

	@NotNull
	public Boolean isSorted()
	{
		return False;
	}

	private static class Entry<V>
	{
		@NotNull
		public V data;

		@Nullable
		public Entry<V> previous;

		@Nullable
		public Entry<V> next;

		public Entry(final V data)
		{
			this.data = data;
		}

		public boolean hasNext()
		{
			return next != null;
		}
	}

	private Entry<V> findEntry(int currentIndex)
	{
		final boolean iterateFromFirst = currentIndex < (size / 2);
		Entry<V> current;
		if (iterateFromFirst)
		{
			current = first;
			while (currentIndex-- > Zero)
			{
				current = current.next;
			}
		}
		else
		{
			current = last;
			while (++currentIndex < size)
			{
				current = current.previous;
			}
		}
		return current;
	}
}
